package top.goldenyear.porpoise.infra.storage.core.service.local;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.time.Duration;
import java.util.Collection;
import java.util.concurrent.locks.ReentrantLock;

import cn.hutool.v7.core.io.file.FileUtil;
import org.noear.solon.Utils;
import org.noear.solon.core.util.IoUtil;
import top.goldenyear.porpoise.common.core.model.Ret;
import top.goldenyear.porpoise.infra.storage.config.FileConfig;
import top.goldenyear.porpoise.infra.storage.core.model.Media;
import top.goldenyear.porpoise.infra.storage.core.service.FileClient;
import top.goldenyear.porpoise.infra.storage.core.service.exception.PorpoiseFileException;
import top.goldenyear.porpoise.infra.storage.core.util.BucketUtils;

import static top.goldenyear.porpoise.infra.storage.core.enums.FileConstant.DEFAULT_BUCKET;

/**
 * 本地存储实现
 *
 * @author airhead
 */
public class LocalFileClient implements FileClient {
  private final String configId;
  private final String defaultBucket;
  private final File root;
  private final ReentrantLock SYNC_LOCK = new ReentrantLock();

  public LocalFileClient(String configId, FileConfig fileConfig) {
    this.configId = configId;

    String bucket = fileConfig.getBucket();
    this.defaultBucket = BucketUtils.justify(bucket, DEFAULT_BUCKET);

    String endpoint = fileConfig.getEndpoint();
    this.root = new File(endpoint);
    FileUtil.mkdir(root);
  }

  @Override
  public String getConfigId() {
    return configId;
  }

  @Override
  public boolean exists(String bucket, String key) throws PorpoiseFileException {
    bucket = BucketUtils.justify(bucket, defaultBucket);

    try {
      return getFile(bucket, key).exists();
    } catch (Throwable e) {
      throw new PorpoiseFileException(e);
    }
  }

  @Override
  public String getTempUrl(String bucket, String key, Duration duration) throws PorpoiseFileException {
    throw new UnsupportedOperationException();
  }

  @Override
  public Media get(String bucket, String key) throws PorpoiseFileException {
    bucket = BucketUtils.justify(bucket, defaultBucket);

    try {
      File file = getFile(bucket, key);

      if (file.exists()) {
        String contentType = Utils.mime(file.getName());
        return new Media(new FileInputStream(file), contentType);
      } else {
        return null;
      }
    } catch (Throwable e) {
      throw new PorpoiseFileException(e);
    }
  }

  @Override
  public Ret put(String bucket, String key, Media media) throws PorpoiseFileException {
    bucket = BucketUtils.justify(bucket, defaultBucket);


    try {
      File file = getFile(bucket, key);
      FileUtil.touch(file);

      try (OutputStream stream = new FileOutputStream(file, false)) {
        IoUtil.transferTo(media.body(), stream);
      }

      return Ret.okWithData(file.getAbsolutePath());
    } catch (Throwable e) {
      throw new PorpoiseFileException(e);
    }
  }

  @Override
  public Ret delete(String bucket, String key) throws PorpoiseFileException {
    bucket = BucketUtils.justify(bucket, defaultBucket);


    try {
      File file = getFile(bucket, key);
      FileUtil.del(file);
      return Ret.okWithData(file.getAbsolutePath());
    } catch (Throwable e) {
      throw new PorpoiseFileException(e);
    }
  }

  @Override
  public Ret deleteBatch(String bucket, Collection<String> keys) throws PorpoiseFileException {
    bucket = BucketUtils.justify(bucket, defaultBucket);


    try {
      for (String key : keys) {
        File file = getFile(bucket, key);
        FileUtil.del(file);
      }

      return Ret.ok();
    } catch (Throwable e) {
      throw new PorpoiseFileException(e);
    }
  }

  private File getFile(String bucket, String key) {
    bucket = BucketUtils.justify(bucket, defaultBucket);

    File dir = new File(root, bucket);

    SYNC_LOCK.lock();

    try {
      FileUtil.mkdir(dir);

      int last = key.lastIndexOf('/');
      if (last > 0) {
        String dir2Str = key.substring(0, last);
        File dir2Tmp = new File(dir, dir2Str);
        FileUtil.mkdir(dir2Tmp);
      }
    } finally {
      SYNC_LOCK.unlock();
    }

    return new File(dir, key);
  }
}
